using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml.Serialization;
using System.Reflection;
using System.Xml;

namespace NCommet.Core
{
    /// <summary>
    /// ContentBase defines the content of an Item. All content classes must inherit ContentBase.
    /// Persistence is done using XML Serialization for the entire content object.
    /// That technique allows persisting arbitrary content, as long as it's xml serializable.
    /// </summary>
    [Serializable]
    public abstract class ContentBase : ICloneable
    {
        private Item container;

        /// <summary>
        /// The item that contains this content.
        /// </summary>
        [XmlIgnore]
        public virtual Item Container
        {
            get { return container; }
            set { container = value; }
        }

        private int id;

        /// <summary>
        /// The id of this content. Persister manages this property.
        /// </summary>
        [XmlIgnore]
        public virtual int Id { get { return id; } set { id = value; } }

        /// <summary>
        /// This special property contains the xml serialized data that fully represent this content object.
        /// Getting the property results to a call to <see cref="GetSerializedData"/> while setting it
        /// to a call to <see cref="SetSerializedData" />
        /// </summary>
        protected virtual string SerializedData
        {
            get { return GetSerializedData(); }
            set { SetSerializedData(value); }
        }

        #region Protected Methods

        /// <summary>
        /// Serializes the entire content object and returns the serialized data as a string.
        /// </summary>
        /// <returns>The serialized content object.</returns>
        /// <remarks>
        /// XML serialization is used to serialize the content object.
        /// </remarks>
        protected string GetSerializedData()
        {
            StringBuilder sb = new StringBuilder();
            string serializationOutput = null;
            XmlSerializer ser = new XmlSerializer(this.GetType());
            using (XmlWriter writer = XmlWriter.Create(sb))
            {
                ser.Serialize(writer, this);
                serializationOutput = sb.ToString();
            }
            return serializationOutput;
        }

        /// <summary>
        /// Creates a new object by deserializing the xml serialized data given in the parameter
        /// and reflectively copies each property of the new object to this object.
        /// </summary>
        /// <param name="value">The serialized content object.</param>
        /// <remarks>
        /// XML serialization is used to deserialize the xml serialized content object.
        /// </remarks>
        protected void SetSerializedData(string value)
        {
            XmlSerializer ser = new XmlSerializer(this.GetType());
            ContentBase obj;
            using (StringReader reader = new StringReader(value))
            {
                obj = (ContentBase)ser.Deserialize(reader);
                foreach (FieldInfo piFrom in (this.GetType()).GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public))
                {
                    if (piFrom.DeclaringType != typeof(ContentBase)) // was: DeclaringType
                        piFrom.SetValue(this, piFrom.GetValue(obj));
                }
            }
        }
        #endregion

        #region ICloneable Members

        /// <summary>
        /// Clones this content object.
        /// </summary>
        /// <returns></returns>
        public virtual object Clone()
        {
            ContentBase result = (ContentBase)GetType().GetConstructor(Type.EmptyTypes).Invoke(null);
            result.SerializedData = this.SerializedData;
            return result;
        }

        #endregion

        /// <summary>
        /// This method is called when an item and its content get cloned, to manage post-clone specific actions on the content.
        /// </summary>
        /// <param name="oldClone">The content object from which the clone was created.</param>
        internal protected virtual void AfterClone(ContentBase oldClone)
        {
        }
    }

    /// <summary>
    /// An attribute that marks ContentBase fields or properties of subclasses,
    /// to allow them to be used by <see cref="NCommet.Core.Agents.ISorter">ISorter</see>.
    /// </summary>
    public class IndexableAttribute : Attribute
    {
        private int key;

        public IndexableAttribute(int key)
        {
            this.key = key;
        }

        /// <summary>
        /// Returns the key that is used to distinguish between the fields available to the sorter.
        /// </summary>
        public int Key { get { return key; } }
    }
}